package studyguide.ch11;

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class GameMidlet extends MIDlet {

	private static final int[] STOPPED_FRAMESEQUENCE = new int[] {0};
	// Only change the image at every 3 frames:
	private static final int[] MOVING_Y_FRAMESEQUENCE = new int[] {1, 1, 1, 2, 2, 2};
	private static final int[] MOVING_X_FRAMESEQUENCE = new int[] {3, 3, 3, 4, 4, 4};
	
	private final Command exitCommand;
	private final Display display;
	private GameTask gameTask;
	
	private final MyGameCanvas gameCanvas;
	private final Graphics graphics;
	private final Sprite mainSprite;
	private final Sprite arrowSprite;
	
	private class SampleCommandListener implements CommandListener {
		public void commandAction(Command command, Displayable displayable) {
			if (command == exitCommand) {
				destroyApp(true);
				notifyDestroyed();
			} else {
				throw new RuntimeException("UNKNOWN COMMAND" + command);
			}
		}
	}
	
	private class GameTask extends TimerTask {
		public void run() {
			gameLoop();
		}
	}
	
	public GameMidlet() throws IOException {
		gameCanvas = new MyGameCanvas();
		graphics = gameCanvas.getGraphics();
		// Arrow sprite has only one frame
		arrowSprite = new Sprite(Image.createImage("/game/arrow.png"));
		// Main sprite is an image with 5 frames, each of 20x32 pixels
		mainSprite = new Sprite(Image.createImage("/game/sprite.png"), 20, 32);
		
		
		exitCommand = new Command("Exit", Command.EXIT, 1);
		
		gameCanvas.addCommand(exitCommand);
		
		SampleCommandListener listener = new SampleCommandListener();
		gameCanvas.setCommandListener(listener);
		display = Display.getDisplay(this);
		
		// At start, the character is not moving
		mainSprite.setFrameSequence(STOPPED_FRAMESEQUENCE);
		
		int screenWidth = gameCanvas.getWidth();
		int screenHeight = gameCanvas.getHeight();
		
		// Sets the reference point in the middle of the sprite...
		mainSprite.defineReferencePixel(mainSprite.getWidth()/2, mainSprite.getHeight()/2);
		// and then sets the sprite's reference point in the middle of the screen
		mainSprite.setRefPixelPosition(screenWidth/2, screenHeight/2);
		
		arrowSprite.defineReferencePixel(arrowSprite.getWidth()/2, arrowSprite.getHeight()/2);
		arrowSprite.setRefPixelPosition(screenWidth/4, screenHeight/4);
	}
	
	protected void destroyApp(boolean unconditional) {
		gameTask.cancel();
	}

	protected void pauseApp() {
		gameTask.cancel();
	}

	protected void startApp() throws MIDletStateChangeException {
		display.setCurrent(gameCanvas);
		// Start the game loop
		Timer timer = new Timer();
		gameTask = new GameTask();
		timer.schedule(gameTask, 0, 1000/30); // 30 frames per second
	}

	/**
	 * Game Loop: 
	 *  - Clean screen
	 *  - Draw a black cross
	 *  - Check inputs/process game logic/process collisions
	 *  - Update frames
	 *  - Paint charactes
	 *  - Flush to output
	 */
	protected void gameLoop() {
		// Clear screen 
		graphics.setColor(0x00ffffff);
		graphics.fillRect(0, 0, gameCanvas.getWidth(), gameCanvas.getHeight());
		// Draw a black cross just to show the middle of the screen
		graphics.setColor(0);
		graphics.drawLine(0, gameCanvas.getHeight()/2, gameCanvas.getWidth(), gameCanvas.getHeight()/2);
		graphics.drawLine(gameCanvas.getWidth()/2, 0, gameCanvas.getWidth()/2, gameCanvas.getHeight());
		
		// Process inputs
		processMyGameInputs();
		
		// If main sprite and arrow collides, do not show the  arrow
		if (mainSprite.collidesWith(arrowSprite, true)) {
			arrowSprite.setVisible(false);
		}
		
		// Update the frames state
		mainSprite.nextFrame();
		
		// Paint the screen objects
		arrowSprite.paint(graphics);
		mainSprite.paint(graphics);
		
		// Flush to output
		gameCanvas.flushGraphics();
	}

	/** Controls the state for main sprite movements */
	private boolean wasMovingX = false;
	/** Controls the state for main sprite movements */
	private boolean wasMovingY = false;
	
	/**
	 * Process key inputs. it will:
	 *  - Set the red arrow visible when a key is pressed
	 *  - Turn the arrow to the side which a key was pressed
	 *  - Move the main sprite
	 *  - Control the logic to choose which frame sequence to show (moving to
	 *    the sizes, moving up/down, stopped)
	 */
	private void processMyGameInputs() {
		int keyStates = gameCanvas.getKeyStates();
		int mainSpriteX = mainSprite.getRefPixelX();
		int mainSpriteY = mainSprite.getRefPixelY();

		// Arrow will only be displayed when a key is pressed
		arrowSprite.setVisible(false);
		
		// Process keys:
		if ((keyStates & GameCanvas.UP_PRESSED) > 0) {
			arrowSprite.setVisible(true);
			arrowSprite.setTransform(Sprite.TRANS_ROT270);
			mainSpriteY--;
			if (!wasMovingY) {
				wasMovingY = true;
				mainSprite.setFrameSequence(MOVING_Y_FRAMESEQUENCE);
			}
		}
		if ((keyStates & GameCanvas.DOWN_PRESSED) > 0) {
			arrowSprite.setVisible(true);
			arrowSprite.setTransform(Sprite.TRANS_ROT90);
			mainSpriteY++;
			if (!wasMovingY) {
				wasMovingY = true;
				mainSprite.setFrameSequence(MOVING_Y_FRAMESEQUENCE);
			}
		}
		if ((keyStates & GameCanvas.LEFT_PRESSED) > 0) {
			arrowSprite.setVisible(true);
			arrowSprite.setTransform(Sprite.TRANS_MIRROR);
			mainSpriteX--;
			if (!wasMovingX) {
				wasMovingX = true;
				mainSprite.setFrameSequence(MOVING_X_FRAMESEQUENCE);
			}
		}
		if ((keyStates & GameCanvas.RIGHT_PRESSED) > 0) {
			arrowSprite.setVisible(true);
			arrowSprite.setTransform(Sprite.TRANS_NONE);
			mainSpriteX++;
			if (!wasMovingX) {
				wasMovingX = true;
				mainSprite.setFrameSequence(MOVING_X_FRAMESEQUENCE);
			}
		}
		
		// Update frame sequence when character is stopped
		if (wasMovingX) {
			if (mainSpriteX == mainSprite.getRefPixelX()) {
				wasMovingX = false;
				if (wasMovingY) {
					mainSprite.setFrameSequence(MOVING_Y_FRAMESEQUENCE);
				} else {
					mainSprite.setFrameSequence(STOPPED_FRAMESEQUENCE);					
				}
			}
		}
		if (wasMovingY) {
			if (mainSpriteY == mainSprite.getRefPixelY()) {
				wasMovingY = false;
				mainSprite.setFrameSequence(STOPPED_FRAMESEQUENCE);
				if (wasMovingX) {
					mainSprite.setFrameSequence(MOVING_X_FRAMESEQUENCE);
				} else {
					mainSprite.setFrameSequence(STOPPED_FRAMESEQUENCE);					
				}
			}
		}
		mainSprite.setRefPixelPosition(mainSpriteX, mainSpriteY);
	}
	
}
